/*
 * drivers/pwm/pwm-szhz.c
 *
 * Huazhen pulse-width-modulation controller driver
 *
 * Copyright (c) 2022, Huazhen Corporation.
 * Based on arch/arm/plat-mxc/pwm.c by LuoQiaofa<qiaofa.luo@szhz-it.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

#define DEBUG 1
#define pr_fmt(fmt) "[%s,%d]: " fmt "\n", __func__, __LINE__
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/mm.h>
#include <linux/pwm.h>
#include <linux/platform_device.h>
#include <linux/pinctrl/consumer.h>
#include <linux/slab.h>
#include <linux/reset.h>

#define dbg_print(fmt, arg...) do { pr_info(fmt, ##arg); } while(0)

#define PWM_ENABLE	      (1 << 0)
#define PWM_MODE_POLARITY (1 << 1)

#define ADDR_PWM_MODE         0x00
#define ADDR_PWM_FREQ         0x01
#define ADDR_PWM_DUTY         0x02
#define ADDR_PWM_BRIGHTNESS   0x03

struct szhz_pwm_chip {
	struct pwm_chip chip;
	struct device *dev;

	uint32_t sys_freq;
	struct reset_control*rst;

	unsigned long clk_rate;

	void __iomem *regs;

	unsigned int num_channels;

	/* Maximum IP frequency for given SoCs */
	unsigned long max_frequency;
};

static inline struct szhz_pwm_chip *to_szhz_pwm_chip(struct pwm_chip *chip)
{
	return container_of(chip, struct szhz_pwm_chip, chip);
}

static inline u32 pwm_readl(struct szhz_pwm_chip *chip, unsigned int num)
{
    uint32_t val;

    val= ioread32(chip->regs + (num << 4));
    return val;
	// return readl(chip->regs + (num << 4));
}

static inline void pwm_writel(struct szhz_pwm_chip *chip, unsigned int num,
			     unsigned long val)
{
    iowrite32(val, chip->regs + (num << 4));
}

static int szhz_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
			    int duty_ns, int period_ns)
{
	struct szhz_pwm_chip *pc = to_szhz_pwm_chip(chip);
	unsigned long long duty_cnt = duty_ns, period_cnt;
	unsigned long long rate;
    uint32_t val = 0;
    uint32_t period;
    uint32_t duty;

    if (period_ns < duty_ns) {
        pr_err("hwpwm=%d,invalid pwm para: period_ns=%d, duty_ns=%d", pwm->hwpwm, period_ns, duty_ns);
        return -EINVAL;
    }
    rate = pc->sys_freq;
    rate = 1000000000L / rate;
    period_cnt = period_ns;
    if (period_ns < rate) {
        pr_err("hwpwm=%d,invalid pwm para: period_ns=%lld, max rate=%lld", pwm->hwpwm, period_cnt, rate);
        return -EINVAL;
    }
    period_cnt /= rate;
    duty_cnt /= rate;
    period = period_cnt;
    duty = duty_cnt;

    dbg_print("hwpwm=%d, period=%lld(%08llx),duty=%lld(%08llx)", pwm->hwpwm,\
            period_cnt, period_cnt, duty_cnt, duty_cnt);
    
    // dbg_print("duty_ns=%d,period_ns=%d,hwpwm=%d", duty_ns,period_ns,pwm->hwpwm);
    iowrite32(val, pc->regs + (pwm->hwpwm * 16) + (ADDR_PWM_MODE << 2));
    iowrite32(period, pc->regs + (pwm->hwpwm * 16) + (ADDR_PWM_FREQ << 2));
    iowrite32(duty, pc->regs + (pwm->hwpwm * 16)+ (ADDR_PWM_DUTY << 2));
	val |= PWM_ENABLE;
    iowrite32(val, pc->regs + (pwm->hwpwm * 16) + (ADDR_PWM_MODE << 2));

	return 0;
}

static int szhz_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
{
	struct szhz_pwm_chip *pc = to_szhz_pwm_chip(chip);
	u32 val;

    dbg_print("hwpwm=%d", pwm->hwpwm);

	val = pwm_readl(pc, pwm->hwpwm);
	val |= PWM_ENABLE;
	pwm_writel(pc, pwm->hwpwm, val);

	return 0;
}

static void szhz_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
{
	struct szhz_pwm_chip *pc = to_szhz_pwm_chip(chip);
	u32 val;

    dbg_print("hwpwm=%d", pwm->hwpwm);

	val = pwm_readl(pc, pwm->hwpwm);
	val &= ~PWM_ENABLE;
	pwm_writel(pc, pwm->hwpwm, val);
}

int szhz_set_polarity(struct pwm_chip *chip, 
        struct pwm_device *pwm,
        enum pwm_polarity polarity)
{
	u32 val;
	struct szhz_pwm_chip *pc = to_szhz_pwm_chip(chip);

    dbg_print("hwpwm=%d, polarity=%d", pwm->hwpwm, polarity);

    val= ioread32(pc->regs + (pwm->hwpwm << 4));
	if (PWM_POLARITY_NORMAL == polarity) {
        val &= ~PWM_MODE_POLARITY;
    } else if (PWM_POLARITY_INVERSED) {
        val |= PWM_MODE_POLARITY;
    } else {
        return -EINVAL;
    }
    iowrite32(val, pc->regs + (pwm->hwpwm * 16) + (ADDR_PWM_MODE << 2));
    return 0;
}

static const struct pwm_ops szhz_pwm_ops = {
	.config = szhz_pwm_config,
	.enable = szhz_pwm_enable,
	.disable = szhz_pwm_disable,
    .set_polarity = szhz_set_polarity,
	.owner = THIS_MODULE,
};

static int szhz_pwm_probe(struct platform_device *pdev)
{
	struct szhz_pwm_chip *pwm;
	struct resource *r;
	int ret;

    dbg_print("Enter");

	pwm = devm_kzalloc(&pdev->dev, sizeof(*pwm), GFP_KERNEL);
	if (!pwm)
		return -ENOMEM;

	pwm->dev = &pdev->dev;

	r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	pwm->regs = devm_ioremap_resource(&pdev->dev, r);
	if (IS_ERR(pwm->regs))
		return PTR_ERR(pwm->regs);

	platform_set_drvdata(pdev, pwm);

    ret = of_property_read_u32(pdev->dev.of_node, "clock-frequency", &pwm->sys_freq);
	if (ret) {
        pwm->sys_freq = 100000000; /* default 100 MHz */
    }
    ret = of_property_read_u32(pdev->dev.of_node, "num-channels", &pwm->num_channels);
	if (ret) {
        pwm->num_channels = 1; /* default 1 channel */
    }

	pwm->chip.dev = &pdev->dev;
	pwm->chip.ops = &szhz_pwm_ops;
	pwm->chip.base = -1;
	pwm->chip.npwm = pwm->num_channels;

	ret = pwmchip_add(&pwm->chip);
	if (ret < 0) {
		dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret);
		return ret;
	}

	return 0;
}

static int szhz_pwm_remove(struct platform_device *pdev)
{
	struct szhz_pwm_chip *pc = platform_get_drvdata(pdev);
	unsigned int i;

	if (WARN_ON(!pc))
		return -ENODEV;

	for (i = 0; i < pc->chip.npwm; i++) {
		pwm_writel(pc, i, 0);
	}

	return pwmchip_remove(&pc->chip);
}

#ifdef CONFIG_PM_SLEEP
static int szhz_pwm_suspend(struct device *dev)
{
    return 0;
}

static int szhz_pwm_resume(struct device *dev)
{
    return 0;
}
#endif

static const struct of_device_id szhz_pwm_of_match[] = {
	{ .compatible = "szhz,pwm", .data = NULL},
	{ }
};

MODULE_DEVICE_TABLE(of, szhz_pwm_of_match);

static const struct dev_pm_ops szhz_pwm_pm_ops = {
	SET_SYSTEM_SLEEP_PM_OPS(szhz_pwm_suspend, szhz_pwm_resume)
};

static struct platform_driver szhz_pwm_driver = {
	.driver = {
		.name = "szhz-pwm",
		.of_match_table = szhz_pwm_of_match,
		.pm = &szhz_pwm_pm_ops,
	},
	.probe = szhz_pwm_probe,
	.remove = szhz_pwm_remove,
};

module_platform_driver(szhz_pwm_driver);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("NVIDIA Corporation");
MODULE_ALIAS("platform:tegra-pwm");
